package edu.cu.cs214.hw9.controller;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import edu.cmu.cs214.hw9.json.JSONArray;
import edu.cmu.cs214.hw9.json.JSONObject;
import edu.cmu.cs214.hw9.db.Constants;
import edu.cmu.cs214.hw9.facelook.Post;
/**
 * A controller used that is used while the Facelook App is open
 * @author davidzheng
 *
 */
public class FacelookAppController extends AbstractController{

	/**
	 * Constructor.  Takes in window so that we can give the window pop-up
	 * windows as necessary
	 * @param window The GUI window
	 */
	public FacelookAppController(JFrame window)
	{
		super(window);
	}
	/**
	 * Checks to see if a user exists
	 * @param email the user's unique email
	 * @return true if the user exists, false if not
	 */
	public boolean exists(String email)
	{
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("EXISTS "+email); //send message to server socket
			if(in.readLine().equals("false")) //false return message indicates
												//email doesn't exist
			{
				JOptionPane.showMessageDialog(window, "No user with given e-mail exists",
						"User Not Found", JOptionPane.PLAIN_MESSAGE);
				out.println("END");
				closeConnection();
				return false;
			}
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	/**
	 * Gets the name associated with the email address
	 * @param email the user's email address
	 * @return the userName is the email exists, null if it doesn't or there is an error/exception
	 */
	public String getName(String email)
	{
		String name = null;
		if (!exists(email)) return name;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("NAME "+email); //sends message to server
			name = in.readLine(); //recieves message back, which is the name
			if(name == null)
			{
				JOptionPane.showMessageDialog(window, "An error has occured.  Please restart Facelook",
						"Facelook Failure", JOptionPane.PLAIN_MESSAGE);
				out.println("END");
				closeConnection();
				return null;
			}
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return name;
	}
	/**
	 * Determines whether two users are friends, OR if the first user has
	 * friend requested the second user.  The functionality is determined by
	 * the pending variable; if pending is false, then it will check for the
	 * former; if pending is true, it will check for the latter.
	 * @param friender The first user's email
	 * @param friendee The second user's email
	 * @param pending Whether we want to determine if a friend request exists (true) or if they are friends (false)
	 * @return true if the check is true, false if it isn't
	 */
	public boolean areFriends(String friender, String friendee, boolean pending)
	{
		boolean ret = false;
		String command;
		if (pending) command = "PENDING ";
		else command = "FRIENDS ";
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println(command+friender+":"+friendee);//sends command
			if(in.readLine().equals("true"))//if recieved back is true
				ret = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return ret;
		
	}
	/**
	 * Sends a friend request from the friender to the friendee
	 * @param friender the requesting friend
	 * @param friendee the requested friend
	 * @return true if success, false if not
	 */
	public boolean requestFriend(String friender, String friendee)
	{
		boolean success = false; //to return
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("REQUEST "+friender+":"+friendee); //sends command
			if(in.readLine().equals("true")) //if gets back true
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Allows the friendee (who got friend requested) to accept the friender's
	 * friend request, thus making them friends
	 * @param friender The requester
	 * @param friendee The one being requested at
	 * @return true on success, false on failure
	 */
	public boolean acceptFriend(String friender, String friendee)
	{
		boolean success = false;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("ACCEPT "+friender+":"+friendee);//send message to socket
			if(in.readLine().equals("true"))//recieves true
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Unfriends the hater who doesn't want to be friends anymore, and the poor
	 * victim who is unsuspecting.
	 * @param hater The one requesting to remove friend
	 * @param victim The friend being removed
	 * @return true on success, false on failure
	 */
	public boolean removeFriend(String hater, String victim)
	{
		boolean success = false;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("UNFRIEND "+hater+":"+victim);//send message to socket
			if(in.readLine().equals("true")) //if result is true/successful
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Gets a list of the given user's friends, OR of all of the users who have
	 * unaccepted friend requests of the user.  Functionality depends on the
	 * boolean pending variable; if false, it finds the former; if true, it
	 * finds the latter
	 * @param email the user's email
	 * @param pending true if we want to see friend requesters, false if we want actual friends
	 * @return An arraylist of the desired freind set, or null on error/non-existant email
	 */
	public ArrayList<String> getFriends(String email, boolean pending)
	{
		ArrayList<String> friends = new ArrayList<String>();
		String command;
		if (pending) command = "GETP ";
		else command = "GETF ";
		if (!exists(email)) return null;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println(command+email);//send command
			String jsonText = in.readLine(); //recieves String representation
											//of JSONArray back
			
			//iterate through array and add friends to the ArrayList to return
			JSONArray jsonFriends = new JSONArray(jsonText);
			for(int i = 0; i<jsonFriends.length(); i++)
				friends.add(jsonFriends.getString(i));
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return friends;
	}
	/**
	 * Get's a list of users that the current user is subscribed to.
	 * @param email The user in question
	 * @return an ArrayList of subscriptions' emails, or null on error/nonexistant email
	 */
	public ArrayList<String> getSubscriptions(String email)
	{
		ArrayList<String> subs = new ArrayList<String>();
		if (!exists(email)) return null;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("SUBS "+email);
			String jsonText = in.readLine();
			JSONArray jsonSubs = new JSONArray(jsonText);
			for(int i = 0; i<jsonSubs.length(); i++)
				subs.add(jsonSubs.getString(i));
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return subs;
	}
	/**
	 * Checks to see if the first user is subscribed to the second
	 * @param reader the first user's email
	 * @param author the second user's email
	 * @return the result of the check
	 */
	public boolean isSubscribed(String reader, String author)
	{
		boolean ret = false;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("SUBSCRIBED "+reader+":"+author);//send message to server
			if(in.readLine().equals("true"))//gets message back, if true return true
				ret = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return ret;
		
	}
	/**
	 * Subscribes the first user to the second user's notifications
	 * @param reader the first user
	 * @param author the second user
	 * @return Whether the transaction is successful
	 */
	public boolean subscribeTo(String reader, String author)
	{
		boolean success = false;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("SUBTO "+reader+":"+author);//the command
			if(in.readLine().equals("true")) //if response is true
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Unsubscribes the first user from the second user's notifications
	 * @param reader the first user
	 * @param author the second user
	 * @return true on success, false on failure
	 */
	public boolean unSubscribe(String reader, String author)
	{
		boolean success = false;
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println("UNSUB "+reader+":"+author);//the message sent to server
			if(in.readLine().equals("true"))//the response
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Gets a list of the given user's status updates, OR of the given user's
	 * notificaitons.  Functionality is determined by the status variable; if 
	 * status is true, it does the former; if false, it does the latter
	 * @param email the user's email
	 * @param status true if we want statuses, false if we want notifications
	 * @return an ArrayList of messages, or null on error
	 */
	public ArrayList<Post> getMessages(String email, boolean status)
	{
		if (!exists(email)) return null;
		String command;
		if (status) command ="GETSTAT ";
		else command = "GETNOT ";
		ArrayList<Post> statuses = new ArrayList<Post>();
		try{
			openConnection(Constants.SERVER_PORT0);
			
			out.println(command+email);//sends command to server
			String jsonText = in.readLine(); //recieves a text response reprsenting
											//an array of Strings
			JSONArray jsonPosts = new JSONArray(jsonText);//convert text to JSONArray
			//iterate over the array
			for(int i = 0; i<jsonPosts.length(); i++)
			{
				/*
				 * First get the JSONObject associated with the given String,
				 * then extract the relevant information, then create a Post
				 * object using the information, and adds to ArrayList to be returned
				 */
				JSONObject postData = new JSONObject(jsonPosts.getString(i));
				String post = postData.getString("message");
				Date date = parse(postData.getString("timestamp"));
				
				statuses.add(new Post(email, post, date));
			}
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			closeConnection();
			return null;
		}
		return statuses;
	}
	/**
	 * Posts a status OR a notification.  Functionality is determined by the 
	 * status variable; if true, it does the former; if false, it does the latter
	 * @param email the user's email
	 * @param post the post to be posted
	 * @param status true if posting status, false if posting notification
	 * @return true on success, false on failure
	 */
	public boolean postMessage(String email, Post post, boolean status)
	{
		boolean success = false;
		String command;
		if(status) command = "POSTST ";
		else command="POSTN ";
		try{
			openConnection(Constants.SERVER_PORT0);
			
			JSONObject postObj = new JSONObject();
			postObj.put("timestamp", formatDate(post.getTimeStamp()));//store formatted date
			postObj.put("message", post.getMessage()); //store the message
			
			out.println(command + email+":"+postObj.toString());//send message
			if(in.readLine().equals("true"))//if response is true
				success = true;
			out.println("END");
			closeConnection();
		}
		catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return success;
	}
	/**
	 * Determines and retrieves the user's newsfeed, in a SORTED arrayList, sorted
	 * by timestamp.  They are sorted from most recent to least recent, which is
	 * logical because we want the most recent updates first.
	 * @param email user's email
	 * @return ArrayList of Posts, in order, that represents the news feed
	 */
	public ArrayList<Post> getNewsFeed(String email)
	{
		/*
		 * To do this, we get a list of all the user's friends, and all the user's
		 * subscriptions.  We get the status/notification posts of all the user's
		 * friends, and the notifications of all the user's subscriptions.  We
		 * then get get just the 10 latest posts, determined by timestamp, and return
		 */
		ArrayList<String> friends = getFriends(email, false);
		ArrayList<String> subscriptions = getSubscriptions(email);
		ArrayList<Post> allPosts = new ArrayList<Post>();
		for(String friend:friends)
		{
			allPosts.addAll(getMessages(friend,false));//notifications
			allPosts.addAll(getMessages(friend,true));//statuses
		}
		for(String s:subscriptions)
		{
			allPosts.addAll(getMessages(s,false));//notifications
		}
		return sorted(getLastTenPosts(allPosts));
	}
	/**
	 * Gets the profile feed of the user, as determined by whether or not the
	 * user is a friend or a subscriber of the user of the page in question.
	 * Note that if the user is neither friend nor subscriber, the profile page
	 * will be void of posts, and this method will not be called because the 
	 * GUI recognizes this and handles it
	 * @param email the viewer's email
	 * @param friend true if the viewer is friends with the owner of the page, false if he is a subscriber
	 * @return an ArrayList of posts, in order from latest to most recent
	 */
	public ArrayList<Post> getProfFeed(String email, boolean friend)
	{
		/*
		 * This method gets all the notifications of the user, includes all
		 * statuses of the user IF the viewer and viewee are friends, then
		 * sorts the posts and gets the 10 most recent ones, and returns.
		 */
		ArrayList<Post> allPosts = new ArrayList<Post>();
		allPosts.addAll(getMessages(email,false)); //notifications
		if (friend)
			allPosts.addAll(getMessages(email,true));//statuses
		return sorted(getLastTenPosts(allPosts));
	}
	/**
	 * Takes an arrayList of posts, and determines the 10 most recent posts
	 * of the posts given.
	 * @param posts the arrayList we are extracting from
	 * @return the arrayList of 10 most recent posts
	 */
	private ArrayList<Post> getLastTenPosts(ArrayList<Post> posts)
	{
		/*
		 * works by sorting the given array from most recent to least recent,
		 * then taking the first 10 elements of the sorted list.
		 */
		//if there are less than 10 posts, then we automatically have the last
		//ten posts
		if (posts.size()<10) return posts;
		
		ArrayList<Post> lastTen = new ArrayList<Post>(10);
		ArrayList<Post> sorted = sorted(posts);//sorts the posts
		for(int i = 0;i<10; i++)//gets the first 10 of sorted list
		{
			lastTen.add(i, sorted.get(i));
		}
		return lastTen;
	}
	
	/**
	 * A helper method to turn a Date into a string, since we cannot store
	 * Dates but can store strings in our database
	 * @param date the date to turn into a string
	 * @return the formatted String
	 */
	private String formatDate(Date date)
	{
		DateFormat dateFormat;
		dateFormat = new SimpleDateFormat("MM/dd/yy hh:mm:ss");
		return dateFormat.format(date);
	}
	/**
	 * A helper method to turn a formatted String representing a date into an
	 * actual Date object
	 * @param dateString the formatted string representation of the date
	 * @return the Date
	 */
	private Date parse(String dateString)
	{
		DateFormat dateFormat;
		dateFormat = new SimpleDateFormat("MM/dd/yy hh:mm:ss");
		Date d = null;
		try {
			d= dateFormat.parse(dateString);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return d;
	}
	/**
	 * A helper method that gets the Latest post from a list of posts.  Akin
	 * to a max() function.  We also want to retrieve the index of the post in
	 * the arrayList, so we store both items in a HashMap
	 * @param posts The posts we want to get the latest post of
	 * @return A hashmap with keys "latest" and "index", representing the latest post and the index of it
	 * or null if posts is empty
	 */
	private HashMap<String, Object> getLatest(ArrayList<Post> posts)
	{
		HashMap<String, Object> latestAndIndex = new HashMap<String, Object>();
		//if posts is empty, return empty arrayList
		if (posts.size()==0)
			return null;
		//Set latest to the first element, then compare with the rest of the
		//elements individually, replacing as necessary
		Post latest = posts.get(0);
		int index = 0;
		for(int i = 1;i<posts.size();i++)
		{
			if (posts.get(i).compareTo(latest)>0)
			{
				latest = posts.get(i);
				index = i;
			}
		}
		latestAndIndex.put("latest", latest);
		latestAndIndex.put("index", index);
		return latestAndIndex;
	}
	/**
	 * Sorts the given ArrayList of Posts by the posting time of each message
	 * @param posts the list to sort
	 * @return the sorted arrayList
	 */
	private ArrayList<Post> sorted(ArrayList<Post> posts)
	{
		/*
		 * I do this with a selection sort, because it is super easy,
		 * and we aren't sorting such large lists that its inefficiency
		 * will be noticeable
		 */
		ArrayList<Post> myPosts = posts;
		int size = myPosts.size();
		//if empty, return it.  It's technically sorted
		if (size==0) return posts;
		ArrayList<Post> sorted = new ArrayList<Post>(size);
		for(int i = 0;i<size; i++)
		{
			HashMap<String, Object> latest = getLatest(myPosts);
			Post minPost = (Post) latest.get("latest");
			int index = (Integer) latest.get("index");
			sorted.add(i, minPost);
			myPosts.remove(index);
		}
		return sorted;
	}
	
}
